package tum0r.webengine.annotations.scan

import tum0r.webengine.annotations.*
import tum0r.webengine.config.ApplicationConfig
import tum0r.webengine.config.DatabaseConfig
import tum0r.webengine.enums.ERequestMethod
import tum0r.webengine.enums.database.ETableFieldType.*
import tum0r.webengine.extension.allFiles
import tum0r.webengine.models.core.Action
import tum0r.webengine.models.database.init.Column
import tum0r.webengine.models.mapping.CommandMapping
import tum0r.webengine.models.mapping.MethodParameterMapping
import tum0r.webengine.models.mapping.ServerMapping
import tum0r.webengine.models.mapping.TableMapping
import java.io.File
import java.net.JarURLConnection
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.util.*

/**
 * 说明: 扫描注解类
 *
 * 创建者: tum0r
 *
 * 时间: 2021/8/22 11:43
 */
class ScanAnnotations {
    /**
     * 添加扫描到的类
     *
     * @param[result] 扫描到的类的结果集合
     * @param[filePath] 类文件所在的路径
     * @param[packageName] 类的包名
     */
    private fun addClass(result: ArrayList<Class<*>>, filePath: String, packageName: String) {
        val tempFile = File(filePath)
        if (!tempFile.exists()) return
        if (tempFile.isFile && tempFile.extension.lowercase(Locale.getDefault()) == "class") {
            val targetClass = ScanAnnotations::class.java.classLoader.loadClass(packageName + "." + tempFile.nameWithoutExtension)
            if (!result.contains(targetClass)) result.add(targetClass)
        } else if (tempFile.isDirectory) {
            val files = tempFile.listFiles() ?: return
            for (file in files) {
                var packageNameTemp = packageName
                if (file.isDirectory) packageNameTemp += "." + file.name
                addClass(result, file.path, packageNameTemp)
            }
        }
    }

    /**
     * 扫描某个包下的类
     *
     * @param[packageName] 包名
     */
    fun scanClass(vararg packageName: String): ArrayList<Class<*>> {
        val result = ArrayList<Class<*>>()
        for (packageNameItem in packageName) {
            if (packageNameItem.isBlank()) continue
            val path = packageNameItem.replace(".", "/")
            val dirs = Thread.currentThread().contextClassLoader.getResources(path)
            while (dirs.hasMoreElements()) {
                val url = dirs.nextElement()
                when (url.protocol.lowercase(Locale.getDefault())) {
                    "file" -> addClass(result, URLDecoder.decode(url.file, StandardCharsets.UTF_8), packageNameItem)
                    "jar" -> {
                        val jarEntries = (url.openConnection() as JarURLConnection).jarFile.entries()
                        while (jarEntries.hasMoreElements()) {
                            val entry = jarEntries.nextElement()
                            var name = entry.name
                            if (name[0] == '/') name = name.substring(1)

                            if (name.startsWith(path)) {
                                val index = name.lastIndexOf('/')
                                if (index == -1 || entry.isDirectory || !name.endsWith(".class")) continue
                                result.add(Class.forName("${name.substring(0, index).replace('/', '.')}.${name.substring(packageNameItem.length + 1, name.length - 6)}"))
                            }
                        }
                    }
                }
            }
        }
        return result
    }

    /**
     * 扫描某个包下有某种注解的类
     *
     * @param[packageName] 包名
     */
    private inline fun <reified T : Annotation> scanAnnotationClass(vararg packageName: String): ArrayList<Class<*>> {
        val result = ArrayList<Class<*>>()
        val annotationClass = T::class.java
        for (classInfo in scanClass(*packageName)) if (classInfo.getAnnotation(annotationClass) != null) result.add(classInfo)
        return result
    }

    /**
     * 扫描请求接口和静态资源
     */
    fun scanServers() {
        // region 扫描HTTP接口
        val annotationClass = Server::class.java
        for (server in scanAnnotationClass<Server>(ApplicationConfig.SCAN_SERVERS_PACKAGE)) {
            val classAnnotation = server.getAnnotation(annotationClass)
            val baseMapping = ApplicationConfig.regexRequestURL.replace(
                (if (classAnnotation.mapping.isNotBlank()) {
                    if (classAnnotation.mapping.startsWith("/")) classAnnotation.mapping else "/${classAnnotation.mapping}"
                } else {
                    "/${ApplicationConfig.SERVER_NAME}/${server.canonicalName.replace(".", "/")}"
                } + "/"), "/"
            )
            for (method in server.declaredMethods) {
                if (method.parameterCount == 0 || method.parameterTypes[method.parameterCount - 1] != Action::class.java) continue
                val methodAnnotation = method.getAnnotation(annotationClass)
                var mappingString = baseMapping + method.name
                var requestMethod = classAnnotation.requestMethod
                if (methodAnnotation != null) {
                    if (methodAnnotation.mapping.isNotBlank()) mappingString = methodAnnotation.mapping
                    requestMethod = methodAnnotation.requestMethod
                }
                ApplicationConfig.SERVER_MAPPINGS.add(ServerMapping(mappingString, ApplicationConfig.SERVER_OBJECTS.size, requestMethod, method, ApplicationConfig.SERVER_METHOD_PARAMETERS_MAPPING.size, null, null))

                val parameters = method.parameters
                val parametersName = Array(parameters.size) { "" }
                val targetClass = Array<Class<*>>(parameters.size) { this::class.java }
                for (i in parameters.indices) {
                    val param = parameters[i]
                    val paramAnnotation = param.getAnnotation(Param::class.java)
                    if (paramAnnotation != null) parametersName[i] = paramAnnotation.paramName
                    else parametersName[i] = param.name
                    targetClass[i] = param.type
                }
                ApplicationConfig.SERVER_METHOD_PARAMETERS_MAPPING.add(MethodParameterMapping(parametersName, targetClass))
            }
            ApplicationConfig.SERVER_OBJECTS.add(server.getConstructor().newInstance())
        }
        // endregion

        // region 静态资源扫描
        // 网站图标
        for (i in ApplicationConfig.ICONS.indices)
            ApplicationConfig.SERVER_MAPPINGS.add(ServerMapping(ApplicationConfig.ICONS[i].mapping, -2, ERequestMethod.MIXTURE, null, -1, null, ApplicationConfig.ICONS[i]))

        // 网页
        for (resource in ApplicationConfig.STATIC_RESOURCES) {
            val mappingString = ApplicationConfig.regexRequestURL.replace("/${resource.resourceMapping}/", "/")
            val files = resource.resourceDirectory.allFiles()
            val regex = Regex("${File.separator}{2,}")
            val basePath = regex.replace(resource.resourceDirectory.absolutePath + File.separator, File.separator)
            // 首页
            ApplicationConfig.SERVER_MAPPINGS.add(ServerMapping(mappingString, -1, ERequestMethod.MIXTURE, null, -1, resource.indexFile, null))
            // 静态资源文件夹下所有的文件
            for (file in files)
                ApplicationConfig.SERVER_MAPPINGS.add(ServerMapping(mappingString + file.absolutePath.replaceFirst(basePath, "").replace(File.separator, "/"), -1, ERequestMethod.MIXTURE, null, -1, file, null))
        }
        // endregion
    }

    /**
     * 扫描服务器命令
     */
    fun scanCommands() {
        val commands = scanAnnotationClass<Command>("tum0r.webengine.defaults.command", ApplicationConfig.SCAN_COMMANDS_PACKAGE)
        for (command in commands) {
            for (method in command.declaredMethods)
                ApplicationConfig.COMMAND_MAPPINGS.add(CommandMapping(command.canonicalName, method, ApplicationConfig.COMMAND_OBJECTS.size))
            ApplicationConfig.COMMAND_OBJECTS.add(command.getConstructor().newInstance())
        }
    }

    /**
     * 扫描数据库表
     */
    fun scanTables() {
        val tables = scanAnnotationClass<Table>(ApplicationConfig.SCAN_TABLES_PACKAGE)
        for (table in tables) {
            val fields = table.fields
            if (fields.isEmpty()) continue
            val tableMapping = TableMapping(table.canonicalName, table.simpleName)
            for (field in fields) {
                val annotation = field.getAnnotation(TableField::class.java)
                if (annotation != null && annotation.fieldType == IGNORE) continue
                val column = Column()
                tableMapping.fields.add(column)
                column.field = field
                column.COLUMN_NAME = field.name
                column.COLUMN_TYPE = if (!field.type.isEnum) DatabaseConfig.getConvertedType(field.type.canonicalName) else "INT"
                if (annotation != null) {
                    column.tableField = annotation
                    when (annotation.fieldType) {
                        INDEX, UNIQUE_INDEX, PRIMARY_KEY -> {
                            column.COLUMN_KEY = annotation.fieldType.type
                            column.SUB_PART = annotation.length
                        }
                        AUTO_INCREMENT -> {
                            column.COLUMN_KEY = annotation.fieldType.type
                            column.EXTRA = "auto_increment"
                        }
                        else -> continue
                    }
                }
            }
            tableMapping.fields.sortBy { field -> field.COLUMN_NAME }
            ApplicationConfig.TABLE_MAPPINGS.add(tableMapping)
        }
    }
}